package com.softwarepunk.lambdastyle.checks;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import com.puppycrawl.tools.checkstyle.api.Check;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.Bundle;

import nexj.core.runtime.Context;
import nexj.core.runtime.MinimalContext;
import nexj.core.scripting.Compiler;
import nexj.core.scripting.ConstPair;
import nexj.core.scripting.GlobalEnvironment;
import nexj.core.scripting.Intrinsic;
import nexj.core.scripting.Machine;
import nexj.core.scripting.PCodeFunction;
import nexj.core.scripting.Pair;
import nexj.core.scripting.Parser;
import nexj.core.scripting.SchemeParser;
import nexj.core.scripting.Symbol;
import nexj.core.util.IOUtil;
import nexj.core.util.IdentityHashTab;
import nexj.core.util.Logger;
import nexj.core.util.Lookup;
import nexj.core.util.ObjUtil;
import nexj.core.util.TextPositionReader;
import nexj.core.util.URLUtil;

public class SchemeCheck extends Check
{
   // attributes

   protected String m_sScheme;

   // associations

   protected final static String ID = "com.softwarepunk.lambdastyle";
   protected final Bundle m_bundle = Platform.getBundle(ID);
   protected final ILog m_log = Platform.getLog(m_bundle);

   /**
    * The class logger.
    */
   protected static Logger s_logger = Logger.getLogger(SchemeCheck.class);

   protected final static Symbol CHECKER = Symbol.define("#checker");

   protected final static Symbol PAIR_AST = Symbol.define("#pair-ast");

   protected static Symbol[] s_tokenArray = new Symbol[176];

   static
   {
      Field[] fieldArray = TokenTypes.class.getFields();

      try
      {
         for (int i = 0, nCount = fieldArray.length; i < nCount; i++)
         {
            Field field = fieldArray[i];
            int nMod = field.getModifiers();

            if (Modifier.isStatic(nMod) && Modifier.isFinal(nMod) && field.getType() == Integer.TYPE)
            {
               s_tokenArray[field.getInt(TokenTypes.class)] = Symbol.define(field.getName());
            }
         }
      }
      catch (IllegalAccessException ex)
      {
         ObjUtil.rethrow(ex);
      }
   }

   protected Context m_context;
   protected GlobalEnvironment m_environment;
   protected Machine m_machine;
   protected PCodeFunction m_function;

   public SchemeCheck()
   {
      m_environment = new GlobalEnvironment();
      m_context = new MinimalContext();
      m_context.initialize(null, m_environment);
      m_machine = m_context.getMachine();
      m_environment.defineVariable(CHECKER, this);

      InputStream istream = null;
      Reader reader = null;

      try
      {
         istream = URLUtil.openResource(SchemeCheck.class, "lambdastyle.scm");
         reader = new TextPositionReader(new BufferedReader(new InputStreamReader(istream, IOUtil.ENCODING)));

         Intrinsic.load(reader, "lambdastyle.scm", m_machine);
      }
      catch (IOException ex)
      {
         ObjUtil.rethrow(ex);
      }
      finally
      {
         IOUtil.close(reader);
         IOUtil.close(istream);
      }
      
   }

   // operations

   public int[] getDefaultTokens()
   {
      return new int[] { TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF };
   }

   public void setScheme(File schemeFile)
   {
      if (s_logger.isDebugEnabled())
      {
         s_logger.debug("Reading scheme checks from file: " + schemeFile.getAbsolutePath());
      }

      Reader reader = null;

      try
      {
         Lookup posMap = new IdentityHashTab();
         SchemeParser parser = new SchemeParser(m_environment);
         Compiler compiler = new Compiler();
         String sURL = URLUtil.toURL(schemeFile.getAbsolutePath());
         Object result = null;

         reader = new TextPositionReader(new FileReader(schemeFile));

         for (;;)
         {
            Object expr = parser.parse(reader, posMap);

            if (expr == Parser.EOF)
            {
               break;
            }

            result = m_machine.invoke(compiler.compile(expr, posMap, sURL, m_machine, true), (Pair)null);
         }

         if (result instanceof PCodeFunction)
         {
            m_function = (PCodeFunction)result;
         }
         else
         {
            throw new IllegalArgumentException("Scheme file must return a function");
         }
      }
      catch (IOException ex)
      {
         ObjUtil.rethrow(ex);
      }
      finally
      {
         IOUtil.close(reader);
      }
   }

   public void visitToken(DetailAST ast)
   {
      Lookup pairASTLookup = new IdentityHashTab(512);
      Pair tree = astToList(ast, pairASTLookup);

      m_environment.defineVariable(PAIR_AST, pairASTLookup);
 
      try
      {
         m_machine.invoke(m_function, tree, (Object[])null);
      }
      catch (Throwable ex)
      {
         m_log.log(new Status(Status.ERROR, ID, "Error running a scheme check: " + ex.getMessage(), ex));
      }
      finally
      {
         m_environment.removeVariable(PAIR_AST);
      }
   }

   protected Pair astToList(DetailAST ast, Lookup pairASTLookup)
   {
      return (Pair)nodeToValue(ast, pairASTLookup);
   }

   protected Object nodeToValue(DetailAST ast, Lookup pairASTLookup)
   {
      int nType = ast.getType();
      Symbol sym = s_tokenArray[nType];
      Pair result;

      if (ast.getType() == TokenTypes.IDENT)
      {
         result = new ConstPair(sym, new ConstPair(ast.getText()));
         pairASTLookup.put(result, ast);

         return result;
      }

      DetailAST child = ast.getLastChild();

      if (child == null)
      {
         return sym;
      }

      Pair head = null;

      while (child != null)
      {
         head = new ConstPair(nodeToValue(child, pairASTLookup), head);
         child = child.getPreviousSibling();
      }

      result = new ConstPair(sym, head);
      pairASTLookup.put(result, ast);

      return result;
   }
   
     /*
      * `(CLASS_DEF
      *    (OBJBLOCK
      *       ...
      *       (VARIABLE_DEF
      *          (MODIFIERS
      *             public
      *             final
      *             static
      *          )
      *          (TYPE
      *             int
      *          )
      *          (IDENTIFIER
      *             "STRING_ORDINAL"
      *          )
      *          (ASSIGN
      *             (EXPR
      *                (NUM_INT 0)
      *             )
      *          )
      *          SEMI
      *       )
      *       ...
      *       ; Method #1 for matching children: direct scheme execution... must ret non-#f
      *       ,(lambda children   ; or (child)
      *          (for-each
      *             (lambda (child)
      *                (when (= (car child) 'VARIABLE_DEF)
      *                )
      *             )
      *             children
      *          )
      *          #t
      *       )
      *       
      *       maybe ,(lambda (child) ...) for one child, and ,@(lambda children ...) for multiple?
      *       
      *       
      *       ; But this is cumbersome...
      *       ; Method #2
      *       ; Pattern matching:
      *       (MODIFIERS ,@mods)
      *       (IDENTIFIER ,ident)
      *       that way, we get multiple bindings at once...
      *       
      *    )
      * )
      */
}
